#!/usr/bin/env python
#
#
#Copyright (C) 2007  Andre Schmeisser
#
#This program is free software: you can redistribute it and/or modify
#it under the terms of the GNU General Public License as published by
#the Free Software Foundation, either version 2 of the License, or
#(at your option) any later version.
#

import pygtk
pygtk.require('2.0')
from threading import Timer
from time import sleep
from optparse import OptionParser
import gtk
gtk.gdk.threads_init()
import re
import tabletIO
import os
#timing_cnt = 0
#if timing_cnt:
#    import timeit

class Stroke:
    """Simple class holding point data.
    Automatically keeps track of bounding box if points are added with append().
    Can compute distance of bounding boxes of 2 instances."""
    def __init__(self):
        self.pts = []
        self.minx = self.miny = 1e8
        self.maxx = self.maxy = -1e8
        return
        
    def append(self, x, y):
        """Appends point to end of list and updates bounding box."""
        self.pts.append( (x,y) )
        if x < self.minx: self.minx = x
        if x > self.maxx: self.maxx = x
        if y < self.miny: self.miny = y
        if y > self.maxy: self.maxy = y
        return
    
    def get_bounding_box(self):
        """Returns the bounding box using cached values."""
        return (self.minx, self.miny, self.maxx, self.maxy)        
        
    # TODO: Currently unused, remove if it will not be needed
    def recompute_bounding_box(self):
        """Returns the bounding box by going through all points again.
        Needs to be used if points are modified."""
        xs = [p[0] for p in self.pts]
        ys = [p[1] for p in self.pts]
        self.minx = min(xs)
        self.maxx = max(xs)
        self.miny = min(ys)
        self.maxy = max(ys)
        return (self.minx, self.miny, self.maxx, self.maxy)
        
    def get_distance_from(self, other):
        """Calculates the distance to stroke 'other' regarding the
        bounding boxes. Uses euclidean norm.
        Intersecting bounding boxes result in distance 0."""
        def one_dim_distance(min1, max1, min2, max2):
            dist = 0
            if max1 < min2:      # '1' is left of '2'
                dist = left2 - right1
            elif max2 < min1:    # '1' is right of '2'
                dist = left1 - right2
            #else: intersecting coordinates
            return dist
            
        left1, top1, right1, bottom1 = self.get_bounding_box()
        left2, top2, right2, bottom2 = other.get_bounding_box()
        xdist = one_dim_distance(left1, right1, left2, right2)
        ydist = one_dim_distance(top1, bottom1, top2, bottom2)        
        #TODO possibly use simpler distance norm (max or x+y)?
        return (xdist**2+ydist**2)**0.5

def send_char(char):
    """Invokes the send_key() function of tabletIO module to send the specified
    character to the currently active window using XTestFakeKeyEvent().
    Uses the default keymap to get the keycode, group and level to generate
    the specified character. Converting the group and level to an appropiate
    combination of modifier keys currently only works for SHIFT modifier."""
    if verbose: print "Emiting key", char
    keymap = gtk.gdk.keymap_get_default()
    entries = keymap.get_entries_for_keyval( \
                gtk.gdk.keyval_from_name(char) )
    if not entries: return
    entry = entries[0]
    # look for first entry with "group" set to 0, default to first
    for e in entries:
        if e[1] == 0:
            entry = e
            break
    #TODO get all modifiers to create corresponding group and level
    if entry[2]:        #the 'level' is set, use SHIFT
        modifiers = 1
    else:
        modifiers = 0
    ### Should find correct modifier state, but does not seem to work
    #for ms in range(0,255):
    #    state = self.keymap.translate_keyboard_state(entry[0], ms, entry[1])
    #    if state[0] == gtk.gdk.keyval_from_name(char):
    #        #found modifier state producing correct keyval
    #        modifiers = ms
    #        break
    tabletIO.send_key(entry[0], modifiers)
    if verbose: print "Used keymap entry", entry, "and modifier state", modifiers
    return

class UnistrokeRecognizer:
    """Class to perform unistroke recognition on strokes.
    Stroke definitions are read from a file."""
    def __init__(self, filename, send_keys=False):
        #self.keymap = gtk.gdk.keymap_get_default()
        self.send_keys = send_keys
        self.read_config(filename)
        return
                
    def read_config(self, filename):
        "Reads the configuration file containing stroke definitions."
        if filename:
            f = open(filename, "r")
            self.sequence_map = []
            for s in f:
                s = s.strip()
                if s != "" and s[0] != "#":
                    l = s.split('=')
                    v = l[0].strip()
                    k = l[1].strip()
                    self.sequence_map.append( (re.compile("^"+k+"$"), k, v) )
            f.close()
        return
        
    def recognize_stroke(self, stroke):
        """Takes a stroke object and tries to recognize it."""
        self.stroke = stroke
        self.generate_sequence()
        self.trim_sequence()
        char = self.recognize_sequence()
        self.stroke = None
        if self.send_keys and char:
            send_char(char)
        return char

    # Do nothing. Used for type compatibility with other recognizer classes.
    def force_recognition(self): pass   
        
    def generate_sequence(self):
        """Converts a list of absolute coordinates to a list of
        coordinates in a 3x3 grid."""
        if len(self.stroke.pts) < 5:
            if verbose: print "Stroke too short (" + str(len(self.stroke.pts)) + "), ignored"
            self.sequence = []
            return
        # 3x3 Map for char sequence with duplicated 3rd element to allow
        # indexing with [3]
        seqMap = ( (1,2,3,3),
                   (4,5,6,6),
                   (7,8,9,9),
                   (7,8,9,9) )
        minx, miny, maxx, maxy = self.stroke.get_bounding_box()
        # scale to interval [0,3]x[0,3]
        if ((maxx - minx) == 0) and ((maxy - miny) == 0):   # point
            if verbose: print "No dimensions, ignored"
            self.sequence = []
            return
        elif (maxx-minx) >= 4*(maxy-miny):   # nearly horizontal, ignore y            
            xscale = 3. / (maxx - minx)
            yscale = 0
        elif (maxy-miny) >= 4*(maxx-minx):   # nearly vertical, ignore x
            xscale = 0
            yscale = 3. / (maxy - miny)
        else:   # normal dimensions, scale x and y
            xscale = 3. / (maxx - minx)
            yscale = 3. / (maxy - miny)
            
        scaled = [ ( (p[0] - minx) * xscale,
                     (p[1] - miny) * yscale ) for p in self.stroke.pts ]
        # if one dimension is ignored, use middle row / column
        if xscale == 0:
            self.sequence = [ seqMap[int(p[1])][1] for p in scaled ]
        elif yscale == 0:
            self.sequence = [ seqMap[1][int(p[0])] for p in scaled ]
        else:
            self.sequence = [ seqMap[int(p[1])][int(p[0])] for p in scaled ]
        #print self.scaled
        #print self.sequence
        return
        
    def trim_sequence(self):
        """Removes duplicate grid positions in a stroke sequence.
        E.g. [1,1,5,5,5,9,5] becomes [1,5,9,5]"""
        self.cur = 0
        self.newseq = []
        for i in self.sequence:
            if i != self.cur:
                self.newseq.append(i)
            self.cur = i
        self.sequence = self.newseq
        if verbose: print self.sequence
        return
        
    def seq_to_str(self, l):
        "Converts a list of numbers to a string, e.g. [1,2,3] -> '123'"
        s = ""
        for i in l: s = s + str(i)
        return s
        
    def recognize_sequence(self):
        """Matches a sequence of grid positions against a list of regular
        expressions. If a match is found, the key corresponding to that
        match is send to the window manager to process."""
        if self.sequence == []:
            return
        char = None
        for regexp, seq, c in self.sequence_map:
            #if self.sequence == seq.: self.char = c
            if regexp.match(self.seq_to_str(self.sequence)):
                if not char:
                    char = c
                    #break
                if verbose: print "yes ("+c+"): ", self.seq_to_str(self.sequence), \
                                  "matched by", seq
        if verbose: print "USR: Recognized:", char
        return char

class MultistrokeRecognizer:
    """Class to perform recognition on characters consisting of multiple
    strokes. Uses UnistrokeRecognizer class to recognize a set of "basic
    strokes". A "compound" character then consist of a series of one or more
    basic strokes. The positional arrangement of those strokes can be limited
    to a set of constraints for different characters, so that e.g. P and D can
    be recognized unambiguously.
    Definitions for basic strokes as well as for compound characters are read
    from a file."""
    def __init__(self, basicstroke_filename, compoundchars_filename,
                 unistroke_filename):
        self.complexstrokes = UnistrokeRecognizer(unistroke_filename, False)
        self.basicstrokes = UnistrokeRecognizer(basicstroke_filename, False)
        self.stroke_symbols = []
        self.strokes = []
        self.read_config(compoundchars_filename)
        #print self.stroke_map
        return
        
    def read_config(self, filename):
        """Reads the configuration file which contains definitions of
           characters consisting of multiple strokes."""
        if filename:
            f = open(filename, "r")
            self.stroke_map = []
            for s in f:
                s = s.strip()
                if s != "" and s[0] != "#":
                    l = s.split('=')
                    char = l[0].strip() # The character to be recognized
                    l = l[1].strip().split(';')
                    symbols = l[0].strip().split(',')
                    constraints = None
                    if len(l) > 1:
                        constraints = l[1].strip().split(',')
                    self.stroke_map.append( (symbols, char, constraints) )
            f.close()
        return
    
    def recognize_stroke(self, stroke):
        """Trys to recognize the stroke as a "basic stroke" and if successful adds
        the recognized symbol to the queue.
        If recognition fails it trys to fall back to unistroke recognition with a
        more complex set of stroke definitions."""
        stroke_symbol = self.basicstrokes.recognize_stroke(stroke)
        ###DEBUG CODE for timing
        #if timing_cnt:
        #    self.tmp_a = stroke_symbol
        #    self.tmp_b = stroke
        #    t = timeit.Timer("recognizerWnd.recognizer.basicstrokes.recognize_stroke(recognizerWnd.recognizer.tmp_b)", "from __main__ import recognizerWnd")
        #    secs = t.timeit(timing_cnt)
        #    print ">>> recognize_stroke took", secs, "secs to execute", timing_cnt, "times"
        ###END DEBUG CODE
        if stroke_symbol:
            if verbose: print "MSR: Basic stroke: ", stroke_symbol            
            ###DEBUG CODE for timing
            #if timing_cnt:
            #    t = timeit.Timer("recognizerWnd.recognizer.add_basic_stroke(recognizerWnd.recognizer.tmp_a, recognizerWnd.recognizer.tmp_b)", "from __main__ import recognizerWnd")
            #    secs = t.timeit(timing_cnt)
            #    print ">>> add_basic_stroke took", secs, "secs to execute", timing_cnt, "times"
            ###END DEBUG CODE
            self.add_basic_stroke(stroke_symbol, stroke)
            pass
        else:
            self.force_recognition()
            stroke_symbol = self.complexstrokes.recognize_stroke(stroke)
            if stroke_symbol:
                send_char(stroke_symbol)
        return
        
    def check_constraints(self, strokes, constraints):
        """Checks if a list of strokes satisfies the constraints imposed on their
        position relative to each other.
        For that it computes the combined bounding box of all strokes, divides it
        into a 3x3 grid and checks which parts are crossed by each stroke. Possible
        constraints are that a stroke must or must not cross one or several cells."""
        if not constraints or constraints == []:
            return True
        
        if verbose: print "checking constraints", constraints
        #Compute bounding box of stroke set
        minx = miny = 1e8
        maxx = maxy = -1e8
        for s in strokes:
            bbox = s.get_bounding_box()
            if bbox[0] < minx: minx = bbox[0]
            if bbox[1] < miny: miny = bbox[1]
            if bbox[2] > maxx: maxx = bbox[2]
            if bbox[3] > maxy: maxy = bbox[3]
            
        #For each individual stroke, check its constraints
        for s, constr in zip(strokes, constraints):
            is_in = [False,False,False, False,False,False, False,False,False]
            seqMap = ( (1,2,3,3),
                       (4,5,6,6),
                       (7,8,9,9),
                       (7,8,9,9) )
            xscale = 3. / (maxx - minx)
            yscale = 3. / (maxy - miny)
            for p in s.pts:
                index = seqMap[int((p[1]-miny)*yscale)][int((p[0]-minx)*xscale)]
                is_in[index-1] = True
            if verbose: print is_in
            
            #parse constraint
            # if set to True, field must be set, otherwise must not be set
            mode_check_set = True
            # if set to True, begins a group in which only one of the constraints
            # needs to be fulfilled
            mode_check_any = False
            group_any_ok = False
            for c in constr:
                if c == '+':
                    #return False, if nothing in previous group was ok
                    if mode_check_any and not group_any_ok:
                        if verbose: print "constraint", constr, "failed at", c
                        return False
                    mode_check_set = True
                elif c == '-':
                    if mode_check_any and not group_any_ok:
                        if verbose: print "constraint", constr, "failed at", c
                        return False
                    mode_check_set = False
                elif c == '~':
                    if mode_check_any and not group_any_ok:
                        if verbose: print "constraint", constr, "failed at", c
                        return False
                    mode_check_any = True
                    group_any_ok = False
                else:
                    if is_in[int(c)-1] == mode_check_set: #constraint fulfilled
                        if mode_check_any:
                            group_any_ok = True
                    else:
                        if not mode_check_any:
                            if verbose: print "constraint", constr, "failed at", c
                            return False            
            if mode_check_any and not group_any_ok:
                if verbose: print "constraint", constr, "failed at last group"
                return False
            if verbose: print "constraint", constr, "fulfilled"
        
        if verbose: print "All constraints fullfilled"
        return True
        
    def force_recognition(self):
        """Flushes the recognition queue."""
        while True:
            self.try_recognition(True)
            if len(self.strokes) == 0:
                break
            #remaining strokes could not be recognized,
            #try unistroke definitions for next stroke instead and continue
            stroke_symbol = self.complexstrokes.recognize_stroke( \
                                                    self.strokes[0])
            if stroke_symbol:
                send_char(stroke_symbol)
            self.strokes = self.strokes[1:]
            self.stroke_symbols = self.stroke_symbols[1:]
        self.strokes = []
        self.stroke_symbols = []
        return
     
    def try_recognition(self, force_rec=False):
        """Attempts to find a match for the sequence of basic strokes in the
        recognition queue. If multiple matches are possible the longest match is
        chosen. If the given strokes are a prefix of a possible stroke sequence,
        recognition is delayed until more strokes are provided and an unambiguous
        match is possible or the flushing of the recognition queue is forced."""
        sth_to_recognize = True
        if verbose: print "Trying to recognize:", self.stroke_symbols
        while sth_to_recognize:
            is_prefix = False
            match = None
            longest_match = 0
            for symbols, char, constraints in self.stroke_map:
                if symbols == self.stroke_symbols:
                    if self.check_constraints(self.strokes, constraints):
                        match = char
                        longest_match = len(symbols) #full match is longest possible
                elif len(symbols) > len(self.stroke_symbols):
                    # Recognized strokes are prefix of this character, so
                    # we wait so see what strokes are to come
                    if symbols[:len(self.stroke_symbols)] == self.stroke_symbols:
                        is_prefix = True
                elif len(symbols) < len(self.stroke_symbols):
                    # Recognized strokes start with this character, mark as
                    # possible recognition
                    if symbols == self.stroke_symbols[:len(symbols)]:
                        if len(symbols) > longest_match:
                            if self.check_constraints( \
                                    self.strokes[:len(symbols)], constraints):
                                longest_match = len(symbols)
                                match = char
                    
            if (not is_prefix or force_rec) and match:
                if verbose: print "Recognized", self.stroke_symbols[:longest_match], \
                                  "as", match
                self.stroke_symbols = self.stroke_symbols[longest_match:]
                self.strokes = self.strokes[longest_match:]
                send_char(match)
            else:
                if verbose: print "MSR: Nothing recognized for", self.stroke_symbols, \
                                  "'is_prefix' = ", is_prefix, \
                                  ", 'force_rec' = ", force_rec, \
                                  ", 'match' = ", match
                if is_prefix:
                    sth_to_recognize = False
                else:
                    # no match found and not prefix of possible match:
                    # try unistroke definitions or discard
                    match = self.complexstrokes.recognize_stroke( \
                                                            self.strokes[0])
                    if match:
                        send_char(match)
                    self.strokes = self.strokes[1:]
                    self.stroke_symbols = self.stroke_symbols[1:]
                    
        return
        
        
    def add_basic_stroke(self, stroke_symbol, stroke):
        """Adds a strokes to the recognition queue and starts recognition if
        possible."""
        mindist = 1e8
        for s in self.strokes:
            d = stroke.get_distance_from(s)
            if d < mindist: mindist = d
        if mindist > 250:    #Start new Character
            self.force_recognition()
        self.stroke_symbols.append(stroke_symbol)
        self.strokes.append(stroke)
        if verbose: print self.stroke_symbols
        self.try_recognition()
        return
        
class FSTRecognizer(MultistrokeRecognizer):
    """Uses finite state transducers from the OpenFST library to process
    a series of basic strokes. Currently the model for character recognition
    is automatically created from the rule set for the MultistrokeRecognizer
    class and processing invokes the extern OpenFST binaries (it is assumed
    those can be found in the PATH."""
    def __init__(self, basicstroke_filename, compoundchars_filename,
                 unistroke_filename):
        MultistrokeRecognizer.__init__(self, basicstroke_filename,
                                    compoundchars_filename, unistroke_filename)
        self.create_model()
        return

    def create_model(self):
        self.input_syms = {}
        self.output_syms = {}
        current_isym = 1
        current_osym = 1
        current_state = 0
        f=open('model.fst', 'w')
        for symbols, char, constraints in self.stroke_map:
            if not self.output_syms.has_key(char):
                self.output_syms[char] = current_osym
                current_osym = current_osym+1
            for i in range(len(symbols)):
                if not self.input_syms.has_key(symbols[i]):
                    self.input_syms[symbols[i]] = current_isym
                    current_isym = current_isym+1
                from_state = current_state
                to_state = current_state+1
                isym = symbols[i]
                osym = "<eps>"
                weight = 0
                if i == 0: from_state = 0
                if i == len(symbols)-1: osym = char
                current_state = current_state + 1
                f.write(str(from_state) + " " + str(to_state) + " " + \
                        isym + " " + osym + " " + str(weight) + "\n")
            # write loop to start state
            f.write(str(current_state) + " 0 <eps> <eps> 10\n")
            # write end-state
            f.write(str(current_state) + " 1\n")
        f.close()
        
        if verbose: print "input symbols:", self.input_syms
        f = open('isym.txt', 'w')
        f.write("<eps> 0\n")
        for k, v in self.input_syms.iteritems():
            f.write(k + " " + str(v) + "\n")
        f.write("\n")
        f.close()
        
        if verbose: print "output symbols:", self.output_syms
        f = open('osym.txt', 'w')
        f.write("<eps> 0\n")
        for k, v in self.output_syms.iteritems():
            f.write(k + " " + str(v) + "\n")
        f.write("\n")
        f.close()
        
        #compile model FST
        os.system("fstcompile --isymbols=isym.txt --osymbols=osym.txt "\
                  "--keep_isymbols --keep_osymbols model.fst model.fst")
        #arc sort model
        os.system("fstarcsort --sort_type=ilabel model.fst model_sorted.fst")
        return
                 
    def try_recognition(self, force_rec=False):
        pass
        
    def force_recognition(self):
        f = open('input.fst', 'w')
        current_state = 0
        for sym in self.stroke_symbols:
            f.write(str(current_state) + " " + str(current_state+1) + " " + \
                    sym + " " + sym + "\n")
            current_state = current_state + 1
        f.write(str(current_state) + " 0\n")
        f.close()
        if verbose: print "Wrote Input to 'input.fst'"
        #compile input FST
        os.system("fstcompile --isymbols=isym.txt --osymbols=isym.txt "\
                  "--keep_isymbols --keep_osymbols input.fst input.fst")
        os.system("fstcompose input.fst model_sorted.fst comp.fst")
        #os.system("fstproject --project_output comp.fst result.fst")
        os.system("fstshortestpath comp.fst result.fst")
        os.system("fsttopsort result.fst result.fst")
        os.system("fstprint result.fst text.fst")
        
        #parse output FST to get resulting text
        try:
            f = open('text.fst', 'r')
            for s in f:
                line = s.split()
                if len(line) >= 4:
                    if line[3] != "<eps>":
                        print line[3]
                        #output this character
                        send_char(line[3])
            f.close()
        except IOError:
            print "IOError: Could not read file text.fst"
        os.system("rm input.fst comp.fst result.fst text.fst")
        self.strokes = []
        self.stroke_symbols = []        
        return
        

class RecognizerWindow:
    """Provides a GTK window which processes input events from a graphics
    tablet, segments those input events into several strokes through timer
    and distance thresholds, and displays them graphically."""
    def __init__(self, options):
        if options.unistroke_only:
            print "Using UNISTROKE recognition only"
            self.recognizer = UnistrokeRecognizer(options.unistroke_filename,
                                                  True)
        else:
            if options.use_fst:
                self.recognizer = FSTRecognizer(options.basicstrokes_filename,
                                                options.compoundchars_filename,
                                                options.unistroke_filename )
            else:
                self.recognizer = MultistrokeRecognizer( \
                                                options.basicstrokes_filename,
                                                options.compoundchars_filename,
                                                options.unistroke_filename )
        self.num_draw_strokes = 8   # how many strokes to keep visible
        self.in_stroke = False
        self.last_x = self.last_y = -1000
        self.last_event = -1
        self.stroke = Stroke()
        self.strokes = []
        self.stroke_interval_threshold = 100
        self.refresh_interval = options.refresh_interval
        self.timer = None
        self.refresh_timer = None
        self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
        if options.unistroke_only:
            self.window.set_title("Python Recognizer [unistroke mode, file: '"\
                                  + options.unistroke_filename + "']")
        else:
            self.window.set_title("Python Recognizer")
        self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG)
        self.window.set_property("accept-focus",0)
        self.window.connect("destroy", self.destroy)
        self.area = gtk.DrawingArea()
        self.area.set_size_request(1200, 300)
        self.pangolayout = self.area.create_pango_layout("")
        self.window.add(self.area)
        self.area.set_events(gtk.gdk.POINTER_MOTION_MASK ) #|
        #                     gtk.gdk.BUTTON_PRESS_MASK |
        #                     gtk.gdk.BUTTON_RELEASE_MASK )
        #self.area.add_events(gtk.gdk.PROXIMITY_IN_MASK | gtk.gdk.PROXIMITY_OUT_MASK)
        self.area.set_extension_events(gtk.gdk.EXTENSION_EVENTS_ALL)
        self.area.connect("expose-event", self.area_expose_cb)
        self.area.connect("motion-notify-event", self.motion_notify)
        self.area.connect("motion_notify_event", self.motion_notify)
        #self.area.connect("button-press-event", self.button_press)
        #self.area.connect("button-release-event", self.button_release)
        #self.area.connect("proximity-in-event",  self.proximity_in)
        #self.area.connect("proximity-out-event",  self.proximity_out)
        self.area.show()
        self.colors = [(0,0,0), (0,0,65535), (0,65535,0), (65535,0,0)]
        self.window.set_keep_below(True)
        self.window.show()
        self.window.window.set_functions(gtk.gdk.FUNC_MOVE | \
                gtk.gdk.FUNC_MINIMIZE | gtk.gdk.FUNC_CLOSE )
        self.window.window.set_decorations(gtk.gdk.DECOR_ALL)
        width, height = self.window.get_size()
        self.window.move(gtk.gdk.screen_width() - width, gtk.gdk.screen_height() - height)
        
        #Stretch drawing of input to fit in window
        self.x_stretch = 1.0
        self.y_stretch = 1.0
        self.pressure_sensitive = False
        self.filter_pts = 0         # Specifies how many points to ignore
        self.pts_filtered = 0       # " " " points have already been ignored
        l = gtk.gdk.devices_list()
        for d in l:
            if d.source == gtk.gdk.SOURCE_PEN:
                if verbose: print d.name, d.mode, "#Axes:", d.num_axes, d.source
                d.set_mode(gtk.gdk.MODE_SCREEN)
                if (d.name[:2] == "PT"):
                    print "OLPC hardware found"
                    self.filter_pts = 2     # ignore first # points on OLPC
                elif (d.name[:9] == "Wacom Pen") or (d.name[:6] == "stylus"):
                    self.y_stretch = 300.0 / 800.0
                    self.pressure_sensitive = True
                    #self.filter_pts = 2
        self.device_name = options.device_name
        if (self.device_name):
            tabletIO.grab_device(self.area.window, self.device_name)
        else:
            tabletIO.grab_device(self.area.window)
        self.style = self.area.get_style()
        self.gc = self.style.fg_gc[gtk.STATE_NORMAL]
        return
    
    def destroy(self, d):
        if self.timer:
            self.timer.cancel()
        if self.refresh_timer:
            self.refresh_timer.cancel()
        gtk.main_quit()
        return        
        
    def timer_execute(self):
        """This timer is executed when no input events where received for a
        given amount of time. It signals that drawing of the stroke has ended
        and is available for recognition."""
        if verbose: print "<<<<<<< timer executed >>>>>>>"
        # prevent concurrent execution with motion_notify(), which should
        # automatically lock because it's a gtk callback
        gtk.gdk.threads_enter()
        self.last_event = -1
        self.end_stroke()
        gtk.gdk.threads_leave()
        return
    
    def refresh_timer_execute(self):
        """Forces the recognizer to flush recognition of all previous strokes.
        Unrecognised strokes are discarded and the display is cleared."""
        gtk.gdk.threads_enter()
        self.recognizer.force_recognition()
        self.strokes = []
        self.draw_strokes()
        gtk.gdk.threads_leave()
        
    def is_pen_event(self, event):
        if (self.device_name):
            if (event.device.name == self.device_name): return True
        else:
            if (event.device.name[:2] == "PT") | \
               (event.device.name[:9] == "Wacom Pen") | \
               (event.device.name[:6] == "stylus"):
            #        print event.type, event.device.name, event.device.source
                    return True
        return False
    
    def motion_notify(self, area, event):
        """GTK callback for motion events.
        Checks if the event should belong to the same stroke as the previous one and
        accordingly appends the coordinate point to that stroke or starts a new 
        stroke. Also updates stroke-ending and refresh timers as well as the
        display."""
        def is_new_stroke():
            time_dif = event.get_time() - self.last_event
            if (time_dif > self.stroke_interval_threshold):
                return True
            #Events are at least 50ms and 40 pixel apart
            elif (time_dif > 50) and (event.x_root - self.last_x)**2 + \
                                     (event.y_root - self.last_y)**2 > 1600:
                return True
            return False
    
        #print event.x_root, event.y_root
        if self.is_pen_event(event):
            # ignore events without pressure on pressure sensitive devices
            if self.pressure_sensitive and \
               event.get_axis(gtk.gdk.AXIS_PRESSURE) < 0.05:
                    return True
            if self.in_stroke:
                if not is_new_stroke():
                    self.area.window.draw_line(self.gc,
                                             int(self.last_x * self.x_stretch),
                                             int(self.last_y * self.y_stretch),
                                             int(event.x_root * self.x_stretch),
                                             int(event.y_root * self.y_stretch))
                    self.add_point( (event.x_root, event.y_root) )
                else:
                    if verbose:
                        print "Distance^2:", ((event.x_root - self.last_x)**2 + \
                                              (event.y_root - self.last_y)**2)
                        print "Time: ", event.get_time() - self.last_event
                    self.end_stroke()
                    self.start_stroke( (event.x_root, event.y_root) )
            else:
                self.start_stroke( (event.x_root, event.y_root) )
            self.last_event = event.get_time()
            if self.timer:
                self.timer.cancel()
            self.timer = Timer(self.stroke_interval_threshold*3 / 1000.0, \
                               self.timer_execute);
            self.timer.start()
            if self.refresh_timer:
                self.refresh_timer.cancel()
        return True
        
    #def proximity_in(self, area, event):
    #    print "Proximity in", event.device.name, event.device.source
    #    return True
    #    
    #def proximity_out(self, area, event):
    #    print "Proximity out"
    #    return True
    #    
    #def button_press(self, area, event):
    #    #if self.is_pen_event(event):
    #    #    self.start_stroke(event.get_root_coords())
    #    return True
    #    
    #def button_release(self, area, event):
    #    #if self.is_pen_event(event):
    #    #    self.end_stroke(event.get_root_coords())
    #    return True
        
    def start_stroke(self, coords):
        self.in_stroke = True
        self.stroke = Stroke()
        self.pts_filtered = 0
        self.add_point(coords)
        #width, height = self.window.get_size()
        #self.window.move(gtk.gdk.screen_width() - width, gtk.gdk.screen_height() - height)
        self.window.set_keep_above(True)
        return
        
    def end_stroke(self, coords=None):
        self.in_stroke = False
        if coords:
            self.add_point(coords)
        self.strokes  = [self.stroke] + self.strokes[:self.num_draw_strokes-1]
        self.draw_strokes()
        #self.window.set_keep_below(True)
        #self.window.resize(50,50)
        #self.window.move(0,gtk.gdk.screen_height() - 30)
        #self.window.hide()
        self.recognizer.recognize_stroke(self.stroke)
        if self.refresh_timer:
            self.refresh_timer.cancel()
        self.refresh_timer = Timer(self.refresh_interval / 1000.0, \
                                   self.refresh_timer_execute);
        self.refresh_timer.start()
        
        #gtk.gdk.threads_leave()
        #self.window.show()
        ##print "Grab device returned:", tabletIO.grab_device(self.area.window)
        return
    
        
    def add_point(self, point):
        if self.pts_filtered < self.filter_pts:
            self.pts_filtered += 1
            if verbose:
                if self.pts_filtered == 1:
                    print "Filtering up to", self.filter_pts, "points"
                print "Filtered point:", point
                #draw filtered points separately
                self.gc.set_rgb_fg_color(gtk.gdk.Color(65535,0,0))
                w = 3
                self.area.window.draw_line(self.gc, self.x_stretch*point[0]-w, \
                                           self.y_stretch*point[1]-w, \
                                           self.x_stretch*point[0]+w, \
                                           self.y_stretch*point[1]+w)
                self.area.window.draw_line(self.gc, self.x_stretch*point[0]-w, \
                                           self.y_stretch*point[1]+w, \
                                           self.x_stretch*point[0]+w, \
                                           self.y_stretch*point[1]-w)
                self.gc.set_rgb_fg_color(gtk.gdk.Color(0,0,0))
        else:
            self.stroke.append(*point)
        self.last_x, self.last_y = point
        return        

    def draw_strokes(self):
        self.area.window.clear()
        #for stroke, color in reversed(zip(self.strokes, self.colors)):
        for i in range(len(self.strokes)):
            stroke = self.strokes[i]
            if i < len(self.colors): color = self.colors[i]
            #else: #keep old color
            if verbose:
                self.draw_helper_grid(stroke)
            self.gc.set_rgb_fg_color(gtk.gdk.Color(*color))
            pts = [ (int(p[0] * self.x_stretch), int(p[1] * self.y_stretch)) \
                    for p in stroke.pts ]
            self.area.window.draw_lines(self.gc, pts)
            if verbose:
                #Higlight the points between lines
                self.gc.set_rgb_fg_color(gtk.gdk.Color(65535,0,65535))
                self.area.window.draw_points(self.gc, pts)
            self.gc.set_rgb_fg_color(gtk.gdk.Color(0,0,0))
        return
        
    def draw_helper_grid(self, stroke):
        """Draws a 3x3 grid around the stroke to help visualize
        how it is recognized."""
        minx, miny, maxx, maxy = stroke.get_bounding_box()#self.get_bounding_box(stroke)
        one_column, one_row = False, False
        if (maxx-minx) >= 4*(maxy-miny):     # nearly horizontal, ignore y
            one_row = True
        elif (maxy-miny) >= 4*(maxx-minx):   # nearly vertical, ignore x
            one_column = True
        minx *= self.x_stretch
        maxx *= self.x_stretch
        miny *= self.y_stretch
        maxy *= self.y_stretch
        
        if one_row or one_column:   #use a different color
            self.gc.set_rgb_fg_color(gtk.gdk.Color(20000,65535,65535))
        else:
            self.gc.set_rgb_fg_color(gtk.gdk.Color(65535,65535,20000))
        # outer rectangle
        self.area.window.draw_rectangle(self.gc, False, int(minx), int(miny),
                                        int(maxx - minx),
                                        int(maxy - miny))
        # inner column rectangle
        if not one_column:
            self.area.window.draw_rectangle(self.gc, False,
                                        int(minx + 1/3. * (maxx - minx)),
                                        int(miny),
                                        int(1/3. * (maxx - minx)),
                                        int(maxy - miny))
        # inner row rectangle
        if not one_row:
            self.area.window.draw_rectangle(self.gc, False,
                                        int(minx),
                                        int(miny + 1/3. * (maxy - miny)),
                                        int(maxx - minx),
                                        int(1/3. *  (maxy - miny)))
        self.gc.set_rgb_fg_color(gtk.gdk.Color(0,0,0))        
        return
    
    def area_expose_cb(self, area, event):
        self.style = self.area.get_style()
        self.gc = self.style.fg_gc[gtk.STATE_NORMAL]
        self.draw_strokes()
        return True

def main():
    gtk.gdk.threads_enter()
    gtk.main()
    gtk.gdk.threads_leave()


global recognizerWnd
recognizerWnd = None

if __name__ == "__main__":
    parser = OptionParser()
    parser.add_option("-s", "--simple", dest="unistroke_only",
                      action="store_true",
                      help="Use only unistroke characters for recognition.")
    parser.add_option("-u", "--unistroke", dest= "unistroke_filename",
                      help="Load unistroke character definitions from FILE.",
                      metavar="FILE")
    parser.add_option("-b", "--basic", dest= "basicstrokes_filename",
                      help="Load definitions for basic strokes from FILE. " \
                      "Is not used if --simple is supplied.",
                      metavar="FILE")
    parser.add_option("-c", "--compound", dest= "compoundchars_filename",
                      help="Load definitions for compound characters, which " \
                      "are made up from several basic strokes (-b), from FILE."\
                      " Is not used if --simple is supplied.",
                      metavar="FILE")
    parser.add_option("-f", "--fst", dest= "use_fst", action="store_true",
                      help="Use FST for recognition. (currently assumes FST "\
                      "binaries are in $PATH)")
    parser.add_option("-v", "--verbose",
                      action="store_true", dest="verbose",
                      help="print status messages to stdout and display grid"\
                      " around strokes")
    parser.add_option("-d", "--device", dest= "device_name",
                      help="Uses tablet device with identifier NAME for input",
                      metavar="NAME")
    parser.add_option("-r", "--refresh", dest= "refresh_interval",
                      help="Defines the refresh interval in ms")
    parser.set_defaults(unistroke_only=False,
                        unistroke_filename="strokechars.txt",
                        basicstrokes_filename="basicstrokes.txt",
                        compoundchars_filename="compoundchars.txt",
                        use_fst=False,
                        verbose=False,
                        device_Name=None,
                        refresh_interval=2000)

    (options, args) = parser.parse_args()
    options.refresh_interval = int(options.refresh_interval)
    verbose = options.verbose
    recognizerWnd = RecognizerWindow(options)
    main()
 
